#!/usr/bin/python
# -*- coding: utf8 -*-

from pyparsing import Literal, ZeroOrMore, Optional, Regex

from expresion import Expresion, Tipo, Exp, TipoExp, TypeException, Id, makeSintaxis, SyntaxException

from lenguaje import Extension

class TipoReg(Tipo):
    
    def __init__(self, tok ):
        """
        El tipo registro estara compuesto por etiquetas con tipos asociados.
        """
        i = 0
        ids = {}
        while(i < len(tok)):
            if tok[i] in ids:
                raise SyntaxException(("Los labels del registro deben ser todos diferentes, pero %s esta repetido")%tok[i])
            ids[tok[i]] = tok[i+1]
            i = i + 2
        self.ids = ids
        
    def __eq__(self, otro):
        """
        La igualdad de un tipo, con un tipo registro se da si en principio es un
        registro y sus etiquetas coinciden en todos los tipos con los del otro tipo registo.
        Consideramos permutaciones como el mismo tipo
        """
        
        if isinstance(otro, TipoReg):
            res = True
            for each in self.ids:
                res = res and each in otro.ids and self.ids[each] == otro.ids[each]
                if(not res):
                    return res
               
            return len(self.ids) == len(otro.ids)
        else:
            return False

    @classmethod
    def sintaxis(cls):
        campo = makeSintaxis(Id, ':', TipoExp)
        listaCampos = Optional(campo + ZeroOrMore(makeSintaxis(",", campo)))
        reg = makeSintaxis('{', listaCampos,'}')
        return reg
    
    def __str__(self):
        s = '{'
        primero = True
        for each in self.ids:
            if primero:
                primero = False
                s+= str(each)+':'+str(self.ids[each])
            else:
                s+=','+str(each)+':'+str(self.ids[each])
        s += '}'
        return s
    
class Proyeccion(Expresion):
    
    def __init__(self, toks):
        """
        La proyeccion de un registro esta compuesta
        por un registro del cual se proyectara, y la etiqueta
        a proyectar de dicho registro.
        """
        expresion, id = toks
        self.exp = expresion
        self.id = id

    def reducir(self):
        """
        Para reducir una proyeccion de registro, primero se reduce el registro
        que la compone, y posteriormente se proyecta la etiqueta determinada
        por el proyector.
        """
        res = self.exp.reducir()
        return res.ids[self.id]

    def tipar(self, namespace):
        """
        gamma |> M :{ l_i : sigma_i} (i en [1..n])
        -------------------------------------------------
        gamma |> M.l_j : sigma:_j (j en [1..]n])
        """
        tipoExp = self.exp.tipar(namespace)
        # gamma |> M :{ l_i : sigma_i} (i en [1..n])
        if isinstance(tipoExp, TipoReg):
            if self.id in tipoExp.ids:
                return tipoExp.ids[self.id]
            else:
                raise TypeException("El registro no contiene el campo con nombre %s. "% self.id)
        else:
            raise TypeException("La expresión %s no es del tipo registro." % self.exp)
                
    def sustituir(self, var, expresion):
        """
        (R.E) {X <- T} --> (R{X <- T}.E)
        """
        return Proyeccion([self.exp.sustituir(var, expresion), self.id])
        
    @classmethod    
    def sintaxis(cls):
        Res = makeSintaxis('(', Exp, '.', Id, ')')
        return Res
    
    def __str__(self):
        return "(%s.%s)" % (self.exp, self.id )
    
class Registro(Expresion):
    
    def __init__(self, tok):
        """
        Un registro esta compuesto por etiquetas
        y expresiones asociadas a cada una de ellas.
        """
        i = 0
        ids = {}
        while(i < len(tok)):
            if tok[i] in ids:
                raise SyntaxException(("Los labels del registro deben ser todos diferentes, pero %s esta repetido")%tok[i])
            ids[tok[i]] = tok[i+1]
            i = i + 2
        self.ids = ids
        
    def reducir(self):
        """
        El resultado de reducir un registro es otro registro con
        cada una de las expresiones asociadas a sus etiquetas reducidas.
        """
        idRes = []
        for each in self.ids:
            idRes.append(each)
            idRes.append(self.ids[each].reducir())
        return Registro(idRes)
    
    def tipar(self, namespace):
        """
        gamma |> M_i : sigma_i para cada i en [1..n]
        ---------------------------------------------------
        gamma |> { l_i = M_i}  : {l_i:sigma_i} (i en [1..n])
        """
        idRes = []
        for each in self.ids:
            idRes.append(each)
            idRes.append(self.ids[each].tipar(namespace))
        return TipoReg(idRes)
    
    def sustituir(self, var, expresion):
        """
        {A1:E1, A2:E2,...,An:En} {X <- T} --> {A1:E1{X <- T}, A2:E2{X <- T},...,An:En{X <- T}}
        """
        ids = []
        for each in self.ids:
            ids.append(each)
            ids.append(self.ids[each].sustituir(var, expresion))
        return Registro(ids)
        
    @classmethod
    def sintaxis(cls):
        campo = makeSintaxis(Id, ':', Exp)
        listaCampos = Optional(campo + ZeroOrMore(makeSintaxis(",", campo)))
        reg = makeSintaxis('{', listaCampos,'}')
        return reg
    
    def __str__(self):
        s = '{'
        primero = True
        for each in self.ids:
            if primero:
                primero = False
                s+= str(each)+':'+str(self.ids[each])
            else:
                s+=','+str(each)+':'+str(self.ids[each])
        s += '}'
        return s
    
extensionRegistro = Extension("Registros", expresiones=[Proyeccion, Registro], tipos=[TipoReg])
